Custom build commands are now being run
authorPierre Krieger <pierre.krieger1708@gmail.com>
Wed, 22 Oct 2014 21:32:57 +0000 (23:32 +0200)
committerAlex Crichton <alex@alexcrichton.com>
Wed, 5 Nov 2014 19:37:34 +0000 (11:37 -0800)
src/cargo/ops/cargo_rustc/mod.rs
src/cargo/util/toml.rs
tests/test_cargo_compile_custom_build.rs

index 51d0644cffb15c836351efff0cfe65345f0c4c2d..774675405cd29b59eb8c395420845170db41e513 100644 (file)
@@ -115,10 +115,10 @@ pub fn compile_targets<'a>(env: &str, targets: &[&'a Target], pkg: &'a Package,
         }
 
         let compiled = compiled.contains(dep.get_package_id());
-        try!(compile(targets.as_slice(), dep, compiled, &mut cx, &mut queue));
+        try!(compile(targets.as_slice(), dep, pkg, compiled, &mut cx, &mut queue));
     }
 
-    try!(compile(targets, pkg, true, &mut cx, &mut queue));
+    try!(compile(targets, pkg, pkg, true, &mut cx, &mut queue));
 
     // Now that we've figured out everything that we're going to do, do it!
     try!(queue.execute(cx.config));
@@ -127,7 +127,7 @@ pub fn compile_targets<'a>(env: &str, targets: &[&'a Target], pkg: &'a Package,
 }
 
 fn compile<'a, 'b>(targets: &[&'a Target], pkg: &'a Package,
-                   compiled: bool,
+                   root_pkg: &'a Package, compiled: bool,
                    cx: &mut Context<'a, 'b>,
                    jobs: &mut JobQueue<'a, 'b>) -> CargoResult<()> {
     debug!("compile_pkg; pkg={}; targets={}", pkg, targets);
@@ -153,27 +153,6 @@ fn compile<'a, 'b>(targets: &[&'a Target], pkg: &'a Package,
     }
     jobs.enqueue(pkg, jq::StageStart, init);
 
-    // Old custom build system
-    // TODO: deprecated, remove
-    let mut build_cmds = Vec::new();
-    for (i, build_cmd) in pkg.get_manifest().get_build().iter().enumerate() {
-        let work = try!(compile_custom_old(pkg, build_cmd.as_slice(), cx, i == 0));
-        build_cmds.push(work);
-    }
-    let (freshness, dirty, fresh) =
-        try!(fingerprint::prepare_build_cmd(cx, pkg));
-    let desc = match build_cmds.len() {
-        0 => String::new(),
-        1 => pkg.get_manifest().get_build()[0].to_string(),
-        _ => format!("custom build commands"),
-    };
-    let dirty = proc() {
-        for cmd in build_cmds.into_iter() { try!(cmd()) }
-        dirty()
-    };
-    jobs.enqueue(pkg, jq::StageCustomBuild, vec![(job(dirty, fresh, desc),
-                                                  freshness)]);
-
     // After the custom command has run, execute rustc for all targets of our
     // package.
     //
@@ -187,7 +166,20 @@ fn compile<'a, 'b>(targets: &[&'a Target], pkg: &'a Package,
             vec![(rustdoc, KindTarget, desc)]
         } else {
             let req = cx.get_requirement(pkg, target);
-            try!(rustc(pkg, target, cx, req))
+            let mut rustc = try!(rustc(pkg, target, cx, req));
+
+            if target.get_profile().is_custom_build() {
+                for &(ref mut work, _, _) in rustc.iter_mut() {
+                    use std::mem;
+                    let execute_cmd = try!(prepare_execute_custom_build(pkg, root_pkg,
+                                                                        target, cx));
+                    let rustc_cmd = mem::replace(work, proc() Ok(()));
+                    let replacement = proc() { try!(rustc_cmd()); execute_cmd() };
+                    mem::replace(work, replacement);
+                }
+            }
+
+            rustc
         };
 
         let dst = match (target.is_lib(),
@@ -207,7 +199,34 @@ fn compile<'a, 'b>(targets: &[&'a Target], pkg: &'a Package,
             dst.push((job(dirty, fresh, desc), freshness));
         }
     }
-    jobs.enqueue(pkg, jq::StageCustomBuild, builds);
+
+    if builds.len() >= 1 {
+        // New custom build system
+        jobs.enqueue(pkg, jq::StageCustomBuild, builds);
+
+    } else {
+        // Old custom build system
+        // TODO: deprecated, remove
+        let mut build_cmds = Vec::new();
+        for (i, build_cmd) in pkg.get_manifest().get_build().iter().enumerate() {
+            let work = try!(compile_custom_old(pkg, build_cmd.as_slice(), cx, i == 0));
+            build_cmds.push(work);
+        }
+        let (freshness, dirty, fresh) =
+            try!(fingerprint::prepare_build_cmd(cx, pkg));
+        let desc = match build_cmds.len() {
+            0 => String::new(),
+            1 => pkg.get_manifest().get_build()[0].to_string(),
+            _ => format!("custom build commands"),
+        };
+        let dirty = proc() {
+            for cmd in build_cmds.into_iter() { try!(cmd()) }
+            dirty()
+        };
+        jobs.enqueue(pkg, jq::StageCustomBuild, vec![(job(dirty, fresh, desc),
+                                                      freshness)]);
+    }
+
     jobs.enqueue(pkg, jq::StageLibraries, libs);
     jobs.enqueue(pkg, jq::StageBinaries, bins);
     jobs.enqueue(pkg, jq::StageTests, tests);
@@ -288,6 +307,74 @@ fn compile_custom_old(pkg: &Package, cmd: &str,
     })
 }
 
+// Prepares a `Work` that executes the target as a custom build script.
+// `pkg` is the package the build script belongs to, and `root_pkg` is the package
+// Cargo is being run on.
+fn prepare_execute_custom_build(pkg: &Package, root_pkg: &Package, target: &Target,
+                                cx: &mut Context) -> CargoResult<Work> {
+    // TODO: this shouldn't explicitly pass `KindTarget` for dest/deps_dir, we
+    //       may be building a C lib for a plugin
+    let layout = cx.layout(pkg, KindTarget);
+    let output = layout.native(pkg);
+    let old_output = layout.proxy().old_native(pkg);
+
+    // Building the command to execute
+    let to_exec = try!(cx.target_filenames(target));
+    if to_exec.len() >= 2 {
+        return Err(human(format!("custom build script shouldn't have multiple outputs")));
+    }
+    let to_exec = to_exec.into_iter().next();
+    let to_exec = match to_exec {
+        Some(cmd) => cmd,
+        None => return Err(human(format!("failed to determine output of custom build script"))),
+    };
+    let to_exec = layout.root().join(to_exec);
+
+    let profile = target.get_profile();
+    let mut p = process(to_exec, pkg, cx)
+                     .env("OUT_DIR", Some(&output))
+                     .env("CARGO_MANIFEST_DIR", Some(root_pkg.get_manifest_path()
+                                                     .display().to_string()))
+                     .env("NUM_JOBS", profile.get_codegen_units().map(|n| n.to_string()))
+                     .env("TARGET", Some(cx.target_triple()))
+                     .env("DEBUG", Some(profile.get_debug().to_string()))
+                     .env("OPT_LEVEL", Some(profile.get_opt_level().to_string()))
+                     .env("PROFILE", Some(profile.get_env()));
+
+    match cx.resolve.features(pkg.get_package_id()) {
+        Some(features) => {
+            for feat in features.iter() {
+                let feat = feat.as_slice().chars()
+                               .map(|c| c.to_uppercase())
+                               .map(|c| if c == '-' {'_'} else {c})
+                               .collect::<String>();
+                p = p.env(format!("CARGO_FEATURE_{}", feat).as_slice(), Some("1"));
+            }
+        }
+        None => {}
+    }
+
+    let pkg = pkg.to_string();
+
+    Ok(proc() {
+        // TODO: is this necessary? it's already part of layout::prepare
+        try!(if old_output.exists() {
+            fs::rename(&old_output, &output)
+        } else {
+            fs::mkdir(&output, USER_RWX)
+        }.chain_error(|| {
+            internal("failed to create output directory for build command")
+        }));
+
+        try!(p.exec_with_output().map(|_| ()).map_err(|mut e| {
+            e.msg = format!("Failed to run custom build command for `{}`\n{}",
+                            pkg, e.msg);
+            e.mark_human()
+        }));
+        Ok(())
+    })
+}
+
 fn rustc(package: &Package, target: &Target,
          cx: &mut Context, req: PlatformRequirement)
          -> CargoResult<Vec<(Work, Kind, String)> >{
index 01d3beb5d0deb60ccbc4142cc47459837a95c3bc..82435718b59a19569a87ad8a9e48f6e534c99734 100644 (file)
@@ -766,8 +766,10 @@ fn normalize(libs: &[TomlLibTarget],
     fn custom_build_target(dst: &mut Vec<Target>, cmd: &Path,
                            profiles: &TomlProfiles) {
         let profiles = [
-            merge(Profile::default_dev().for_host(true), &profiles.dev),
-            merge(Profile::default_release().for_host(true), &profiles.release),
+            merge(Profile::default_dev().for_host(true).custom_build(true),
+                  &profiles.dev),
+            merge(Profile::default_release().for_host(true).custom_build(true),
+                  &profiles.release),
         ];
 
         let name = format!("build-script-{}", cmd.filestem_str().unwrap_or(""));
index 0853d9dc1e59cb870b2f6dbad1b4795fcbfecbf2..aab7e6556d8603a0ec671c4c0be956dc33ff2194 100644 (file)
@@ -12,7 +12,7 @@ test!(custom_build_compiled {
             name = "foo"
             version = "0.5.0"
             authors = ["wycats@example.com"]
-            build = 'build.rs'
+            build = "build.rs"
         "#)
         .file("src/main.rs", r#"
             fn main() {}
@@ -23,3 +23,100 @@ test!(custom_build_compiled {
     assert_that(p.cargo_process("build"),
                 execs().with_status(101));
 })
+
+test!(custom_build_script_failed {
+    let p = project("foo")
+        .file("Cargo.toml", r#"
+            [project]
+
+            name = "foo"
+            version = "0.5.0"
+            authors = ["wycats@example.com"]
+            build = "build.rs"
+        "#)
+        .file("src/main.rs", r#"
+            fn main() {}
+        "#)
+        .file("build.rs", r#"
+            fn main() {
+                std::os::set_exit_status(101);
+            }
+        "#);
+    assert_that(p.cargo_process("build"),
+                execs().with_status(101)
+                       .with_stderr(format!("\
+Failed to run custom build command for `foo v0.5.0 (file://{})`
+Process didn't exit successfully: `{}` (status=101)",
+p.root().display(), p.bin("build-script-build").display())));
+})
+
+test!(custom_build_env_vars {
+    let p = project("foo")
+        .file("Cargo.toml", r#"
+            [project]
+
+            name = "foo"
+            version = "0.5.0"
+            authors = ["wycats@example.com"]
+
+            [features]
+            bar_feat = ["bar/foo"]
+
+            [dependencies.bar]
+            path = "bar"
+        "#)
+        .file("src/main.rs", r#"
+            fn main() {}
+        "#)
+        .file("bar/Cargo.toml", r#"
+            [project]
+
+            name = "bar"
+            version = "0.5.0"
+            authors = ["wycats@example.com"]
+            build = "build.rs"
+
+            [features]
+            foo = []
+        "#)
+        .file("bar/src/lib.rs", r#"
+            pub fn hello() {}
+        "#);
+
+    let file_content = format!(r#"
+            use std::os;
+            use std::io::fs::PathExtensions;
+            fn main() {{
+                let _target = os::getenv("TARGET").unwrap();
+
+                let _ncpus = os::getenv("NUM_JOBS").unwrap();
+
+                let out = os::getenv("CARGO_MANIFEST_DIR").unwrap();
+                let p1 = Path::new(out);
+                let p2 = os::make_absolute(&Path::new(file!()).dir_path().dir_path());
+                assert!(p1 == p2, "{{}} != {{}}", p1.display(), p2.display());
+
+                let opt = os::getenv("OPT_LEVEL").unwrap();
+                assert_eq!(opt.as_slice(), "0");
+
+                let opt = os::getenv("PROFILE").unwrap();
+                assert_eq!(opt.as_slice(), "compile");
+
+                let debug = os::getenv("DEBUG").unwrap();
+                assert_eq!(debug.as_slice(), "true");
+
+                let out = os::getenv("OUT_DIR").unwrap();
+                assert!(out.as_slice().starts_with(r"{0}"));
+                assert!(Path::new(out).is_dir());
+
+                let _feat = os::getenv("CARGO_FEATURE_FOO").unwrap();
+            }}
+        "#,
+        p.root().join("target").join("native").display());
+
+    let p = p.file("bar/build.rs", file_content);
+
+
+    assert_that(p.cargo_process("build").arg("--features").arg("bar_feat"),
+                execs().with_status(0));
+})